package com.swust.dumpling.commons.utils;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

import com.swust.dumpling.commons.exception.NumOutOfSizeException;

/**
 * @author swust-cs-Dong6 B425
 * RSA工具包, 提供RSA密文密钥生成,公私钥回馈以及密文解密
 * */
public class RSAUtil {

	public static String MESSAGE = "this is for showing detail";

	/**
	 * 32位操作系统中整数最大位    1 << 31 - 1
	 * the maximum bit integer in 32-bit operating system, values 1 << 31 - 1
	 * 
	 * */
	public static final int MAX_SIZE = 0x7fffffff;	
	
	/**
	 * N是由两个素数乘法运算得到的值,素数已经被毁掉
	 * 
	 * N is the value of the multiplication of two prime numbers P & Q,
	 * prime number has been destroyed
	 * */
	private static int NUM_N = 0;

	/**
	 * 公钥和私钥
	 * 
	 * Public and private keys
	 * */
	private static int NUM_E1 = 0;
	private static int NUM_E2 = 0;

	/**
	 * 自定义构造器
	 * 
	 * custom constructor
	 * */
	private RSAUtil(int p , int q , int pub) {

		if (!MathUtil.isPrime(p)) 
			throw new 
			IllegalArgumentException("your parameter p:"+p+"is not a prime");
		if (!MathUtil.isPrime(q))
			throw new 
			IllegalArgumentException("your parameter p:"+q+"is not a prime");
		if (pub <= 0 || pub >= MAX_SIZE / 2)
			throw new 
			UnsupportedOperationException("your parameter public key is not in my support");
		
		/**
		 * ψ (N) 则是N的欧拉值
		 * 
		 * ψ (N) is the Euler value of N
		 * */
		int FAI_N = 0;
		
		NUM_N = q * p;
		FAI_N = (p-1) * (q-1);

		NUM_E1 = pub;

		if (MathUtil.coprime(NUM_E1, FAI_N) != 1) {
			clear(); 
			throw new IllegalArgumentException("Euler number of p * q "+FAI_N
					+" can`t be coprime with your public key "+pub);
		}
		NUM_E2 = MathUtil.getModLower(NUM_E1, FAI_N);

		if (NUM_E2 == 0) {
			clear();
			throw new IllegalArgumentException("Euler number of p * q "+FAI_N
					+"can`t be coprime with your public key"+pub);
		}
		//		String key = "public:{"+NUM_E1+" , "+NUM_N+" } , private:{"+NUM_E2+" , "+NUM_N+" }";
	}
	//constructor
	private RSAUtil() {
		NUM_E1 = 9999991;
		NUM_E2 = 57321811;
		NUM_N = 1073602561;
	}
	
	/**
	 * for getting all message
	 * 
	 * @param puk : public key
	 * @param prk : private key
	 * @param num : the number n
	 * */
	public Map<String , Integer> getRSAkey() {
		Map<String , Integer> map = new HashMap<String , Integer>();
		map.put("puk", NUM_E1);
		map.put("prk", NUM_E2);
		map.put("num", NUM_N);
		return map;
	} 
	
	void clear() {
		NUM_N = 0;
		NUM_E1 = 0;
		NUM_E2 = 0;
	}

	/**
	 * 通过自定义 p,q,public获取RSA
	 * 
	 * get RSA by owner Q ,P , PUB
	 * 
	 * @param p int number p
	 * @param q int number q
	 * @param pub int public key
	 * */
	public static RSAUtil getRSAUtil(int q , int p , int pub ) {
		return new RSAUtil(p , q , pub);
	}

	/**
	 * 对数字进行加密 ....
	 * Encrypt digital ....
	 * */
	public int encodeByRSA(int num){
		return encode(num);
	}
	/**
	 * 对数组进行加密 ....数组长度不为0
	 *  
	 * The array length of the encrypted array is not 0
	 * */
	public int[] encodeByRSA(int[] input) {
		int[] output = input;
		if (input == null || input.length == 0)
			throw new 
			IllegalArgumentException("input doesn`t match : "+(input==null?"no null input.":"no 0 length input"));
		for (int i = 0 ; i < input.length ; i ++) {
			output[i] = encode(input[i]);
		}
		return output;
	}

	/**
	 * 用于RSA的加密运算	
	 * For RSA encoding operation
	 * @param num int the number for decode
	 * @param key int your public or private key
	 * @param n	  int your public number N
	 * @Tip 
	 * we don`t check whether your input is correct.
	 * make sure you have your key and n correct.
	 * @return private or public key of inputting
	 * */
	public static int encodeByRSA(int num , int key , int n)
	{
		if (num <= 0 || key <=0 || n <= 0)
			return 0;
		BigInteger i = BigInteger.ONE;
		BigInteger num1 = new BigInteger(num+"");
		BigInteger n1 = new BigInteger(n+"");
		while (key -- != 0) {
			i = i.multiply(num1);
		}
		return i.mod(n1).intValue();
	}

	/**
	 * 加密核心
	 * 
	 * RSA core
	 * */
	private int encode(int num) {
		if (NUM_N == 0 || NUM_E1 == 0 || NUM_E2 == 0)
			throw new RuntimeException("problem init error ! ");
		if (num == 0)
			return 0;
		if ( num >= NUM_N)
			throw new NumOutOfSizeException("the input number "+num+" is bigger the the number "+NUM_N);
		BigInteger tmp = new BigInteger(num+"");
		BigInteger bi = BigInteger.ONE;
		//加密用的是E1/公钥,解密使用E2/私钥
		int tt = NUM_E1;
		while (tt -- != 0) {
			bi = bi.multiply(tmp);
		}
		return bi.mod(new BigInteger(NUM_N+"")).intValue();
	}
	
	/**
	 * 用于RSA的解码运算	
	 * For RSA decryption operation
	 * @param num int the number for decode
	 * @param key int your public or private key
	 * @param n	  int your public number N
	 * */
	public static int decode(int num , int key , int n)
	{
		if (num <= 0 || key <=0 || n <= 0)
			return 0;
		BigInteger i = BigInteger.ONE;
		BigInteger num1 = new BigInteger(num+"");
		BigInteger n1 = new BigInteger(n+"");
		while (key -- != 0) {
			i = i.multiply(num1);
		}
		return i.mod(n1).intValue();
	}
}
